package gateway

import (
	"encoding/json"
	"mmo/card/internal/protocol"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

const (
	writeWait      = 10 * time.Second
	pongWait       = 60 * time.Second
	pingPeriod     = (pongWait * 9) / 10
	maxMessageSize = 1024 * 64
)

// Client 表示一个连接的客户端
type Client struct {
	id       string
	conn     *websocket.Conn
	send     chan []byte
	manager  *ConnectionManager
	handler  HandlerFunc
	roomID   string
	roomLock sync.RWMutex
}

func NewClient(conn *websocket.Conn, manager *ConnectionManager, handler HandlerFunc) *Client {
	return &Client{
		id:      uuid.NewString(),
		conn:    conn,
		send:    make(chan []byte, 128),
		manager: manager,
		handler: handler,
	}
}

func (c *Client) ID() string {
	return c.id
}

func (c *Client) RoomID() string {
	c.roomLock.RLock()
	defer c.roomLock.RUnlock()
	return c.roomID
}

func (c *Client) setRoomID(roomID string) {
	c.roomLock.Lock()
	defer c.roomLock.Unlock()
	c.roomID = roomID
}

// ReadPump 处理入站消息
func (c *Client) ReadPump() {
	defer func() {
		c.manager.Unregister(c)
		c.conn.Close()
	}()
	c.conn.SetReadLimit(maxMessageSize)
	_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
	c.conn.SetPongHandler(func(string) error {
		_ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
		return nil
	})

	for {
		_, data, err := c.conn.ReadMessage()
		if err != nil {
			break
		}
		var env protocol.Envelope
		if err := json.Unmarshal(data, &env); err != nil {
			_ = c.SendEnvelope(&protocol.Envelope{Type: "error", Payload: []byte(`"invalid json"`)})
			continue
		}
		if env.PlayerID == "" {
			env.PlayerID = c.id
		}
		if err := c.handler(c, &env); err != nil {
			_ = c.SendEnvelope(&protocol.Envelope{Type: "error", Payload: []byte(`"handler error"`)})
		}
	}
}

// WritePump 处理出站消息
func (c *Client) WritePump() {
	ticker := time.NewTicker(pingPeriod)
	defer func() {
		ticker.Stop()
		c.conn.Close()
	}()
	for {
		select {
		case msg, ok := <-c.send:
			_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if !ok {
				_ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			w, err := c.conn.NextWriter(websocket.TextMessage)
			if err != nil {
				return
			}
			if _, err := w.Write(msg); err != nil {
				_ = w.Close()
				return
			}
			_ = w.Close()

		case <-ticker.C:
			_ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

// SendEnvelope 发送 JSON 消息
func (c *Client) SendEnvelope(env *protocol.Envelope) error {
	data, err := json.Marshal(env)
	if err != nil {
		return err
	}
	select {
	case c.send <- data:
		return nil
	default:
		close(c.send)
		return nil
	}
}

// ConnectionManager 管理在线客户端与房间成员
type ConnectionManager struct {
	clients     sync.Map // playerID -> *Client
	roomClients sync.Map // roomID -> *roomBucket
}

type roomBucket struct {
	mu      sync.RWMutex
	members map[string]*Client
}

func NewConnectionManager() *ConnectionManager {
	return &ConnectionManager{}
}

func (cm *ConnectionManager) Register(c *Client) {
	cm.clients.Store(c.ID(), c)
}

func (cm *ConnectionManager) Unregister(c *Client) {
	cm.clients.Delete(c.ID())
	if rid := c.RoomID(); rid != "" {
		cm.LeaveRoom(rid, c.ID())
	}
}

func (cm *ConnectionManager) JoinRoom(roomID, playerID string, c *Client) {
	b := cm.getOrCreateBucket(roomID)
	b.mu.Lock()
	defer b.mu.Unlock()
	if b.members == nil {
		b.members = make(map[string]*Client)
	}
	b.members[playerID] = c
	c.setRoomID(roomID)
}

func (cm *ConnectionManager) LeaveRoom(roomID, playerID string) {
	if rb, ok := cm.roomClients.Load(roomID); ok {
		b := rb.(*roomBucket)
		b.mu.Lock()
		defer b.mu.Unlock()
		delete(b.members, playerID)
	}
}

func (cm *ConnectionManager) BroadcastToRoom(roomID string, env *protocol.Envelope, excludePlayerID string) {
	if rb, ok := cm.roomClients.Load(roomID); ok {
		b := rb.(*roomBucket)
		b.mu.RLock()
		defer b.mu.RUnlock()
		for pid, cli := range b.members {
			if excludePlayerID != "" && pid == excludePlayerID {
				continue
			}
			_ = cli.SendEnvelope(env)
		}
	}
}

func (cm *ConnectionManager) getOrCreateBucket(roomID string) *roomBucket {
	if rb, ok := cm.roomClients.Load(roomID); ok {
		return rb.(*roomBucket)
	}
	b := &roomBucket{members: make(map[string]*Client)}
	cm.roomClients.Store(roomID, b)
	return b
}
